/*
* Sun Public License Notice
*
* The contents of this file are subject to the Sun Public License
* Version 1.0 (the "License"). You may not use this file except in
* compliance with the License. A copy of the License is available at
* http://www.sun.com/
*
* The Original Code is Forte for Java, Community Edition. The Initial
* Developer of the Original Code is Sun Microsystems, Inc. Portions
* Copyright 1997-2000 Sun Microsystems, Inc. All Rights Reserved.
*/
package org.openide.explorer.view;
import java.awt.*;
import java.awt.event.*;
import java.beans.*;
import java.io.*;
import java.util.*;
import java.net.URL;
import javax.swing.*;
import javax.swing.event.*;
import org.openide.TopManager;
import org.openide.explorer.*;
import org.openide.nodes.*;
import org.openide.util.WeakListener;
import org.openide.util.Mutex;
import org.openide.util.NbBundle;
import org.openide.util.HelpCtx;
import org.openide.awt.JPopupMenuUtils;
/** An explorer view that shows the context hierarchy in
* a popup menu. Initially, it shows a left button which opens a popup
* menu from the root context and a right button which opens a popup menu from the currently
* explored context.
*
* @author Ian Formanek, Jaroslav Tulach
*/
public class MenuView extends JPanel {
/** generated Serialized Version UID */
static final long serialVersionUID = -4970665063421766904L;
/** The explorerManager that manages this view */
transient private ExplorerManager explorerManager;
/** button to open root view */
private JButton root;
/** button to open view from current node */
private JButton current;
/** property change listener */
transient private Listener listener;
/* This is the constructor implementation
* recommended by ExplorerView class that only calls the inherited
* constructor and leaves the initialization for method initialize().
* @see #initialize */
/** Construct a new menu view.
*/
public MenuView () {
setLayout (new java.awt.FlowLayout());
root = new JButton(NbBundle.getBundle (MenuView.class).getString("MenuViewStartFromRoot"));
add (root);
current = new JButton(NbBundle.getBundle (MenuView.class).getString("MenuViewStartFromCurrent"));
add (current);
init ();
}
/** Initializes listeners */
private void init () {
root.addMouseListener (listener = new Listener (true));
current.addMouseListener (new Listener (false));
}
private void readObject (ObjectInputStream ois) throws IOException, ClassNotFoundException {
ois.defaultReadObject ();
init ();
}
/* Initializes view.
*/
public void addNotify() {
super.addNotify ();
explorerManager = ExplorerManager.find (this);
explorerManager.addPropertyChangeListener (listener);
doChecks ();
}
/* Deinitializes view.
*/
public void removeNotify() {
super.removeNotify ();
explorerManager.removePropertyChangeListener (listener);
explorerManager = null;
}
/** Does some checks */
private void doChecks () {
current.setEnabled (explorerManager.getSelectedNodes ().length == 1);
}
/** Listener that opens the menu and listens to its actions
*/
private class Listener extends MouseAdapter
implements Acceptor, PropertyChangeListener {
/** from root */
private boolean root;
public Listener (boolean root) {
this.root = root;
}
public void mousePressed (MouseEvent e) {
if (e.getComponent ().isEnabled ()) {
// open the popup menu
Node context = null;
if (!root) {
Node[] sel = explorerManager.getSelectedNodes ();
if (sel.length > 0) {
context = sel[0];
}
}
if (context == null) {
context = explorerManager.getRootContext();
}
Menu menu = new Menu (context, listener);
JPopupMenu popupMenu = menu.getPopupMenu ();
java.awt.Point p = new java.awt.Point (e.getX (), e.getY ());
p.x = e.getX() - p.x;
p.y = e.getY() - p.y;
SwingUtilities.convertPointToScreen (p, e.getComponent ());
Dimension popupSize = popupMenu.getPreferredSize ();
Dimension screenSize = Toolkit.getDefaultToolkit ().getScreenSize ();
if (p.x + popupSize.width > screenSize.width) p.x = screenSize.width - popupSize.width;
if (p.y + popupSize.height > screenSize.height) p.y = screenSize.height - popupSize.height;
SwingUtilities.convertPointFromScreen (p, e.getComponent ());
popupMenu.show(e.getComponent (), p.x, p.y);
}
}
public boolean accept (Node n) {
try {
Node parent = n.getParentNode ();
if (parent != null) {
explorerManager.setExploredContext (parent);
}
explorerManager.setSelectedNodes (new Node[] { n });
return true;
} catch (PropertyVetoException ex) {
return false;
}
}
public void propertyChange (PropertyChangeEvent ev) {
if (ExplorerManager.PROP_SELECTED_NODES.equals (ev.getPropertyName ())) {
doChecks ();
}
}
}
/** Menu item representing a node (with children) in a menu hierarchy.
* One can attach an acceptor to the menu that will be informed
* each time a user selects an item whether
* to close the menu or not.
*/
public static class Menu extends org.openide.awt.JMenuPlus {
/** not null if the submenus has not been searched yet */
private JMenuItem empty;
/** node change listener */
private Listener listener;
/** map from Nodes to JMenuItems. (Node, JMenuItem)
* @associates JMenuItem*/
private HashMap map;
/** the visualizer for the given node */
private VisualizerNode vis;
/** The node represented. */
protected Node node;
/** Action listener to attach to all menu items. */
protected Acceptor action;
static final long serialVersionUID =-1505289666675423991L;
/** Constuctor that assigns the node a default
* action, e.g. to open the Explorer or a property sheet.
* @param node node to represent
*/
public Menu (Node node) {
this (node, DEFAULT_LISTENER);
}
/** Constructor that permits specification of the action on the node.
*
* @param node node to represent
* @param action action called when node is selected
*/
public Menu (Node node, Acceptor action) {
this (node, action, true);
}
/** Constructor that permits specification of the action on the node,
* and permits overriding the name and icon of the menu.
*
* @param node node to represent
* @param action action called when node selected
* @param setName <code>true</code> to automatically set the name and icon of the item
*/
public Menu (final Node node, Acceptor action, boolean setName) {
this.node = node;
this.action = action;
this.vis = VisualizerNode.EMPTY;
// initialize the visualizer
Mutex.EVENT.readAccess (new Runnable () {
public void run () {
vis = VisualizerNode.getVisualizer (null, node);
}
});
listener = new Listener ();
vis.addNodeModel (listener);
getPopupMenu ().addPopupMenuListener (listener);
MenuSelectionManager.defaultManager ().addChangeListener (
WeakListener.change (listener, MenuSelectionManager.defaultManager ())
);
if (setName) {
MenuItem.initialize (this, node);
}
HelpCtx help = node.getHelpCtx ();
if (help != null && ! help.equals (HelpCtx.DEFAULT_HELP) && help.getHelpID () != null)
HelpCtx.setHelpIDString (this, help.getHelpID ());
}
/** Checks for {@link MouseEvent#isPopupTrigger right click} to ask the acceptor whether
* to accept the selection.
* @param e the mouse event
* @param path used by the superclass
* @param manager used by the superclass
*/
public void processMouseEvent(MouseEvent e, MenuElement[] path, MenuSelectionManager manager) {
super.processMouseEvent (e, path, manager);
if (e.isPopupTrigger () && action.accept (node)) {
MenuSelectionManager.defaultManager ().clearSelectedPath ();
}
}
/** Create a menu element for a node. The default implementation creates
* {@link MenuView.MenuItem}s for leafs and <code>Menu</code> for other nodes.
*
* @param n node to create element for
* @return the created node
*/
protected JMenuItem createMenuItem (Node n) {
return n.isLeaf () ?
(JMenuItem) new MenuItem (n, action) :
(JMenuItem) new Menu (n, action);
}
/** Changes elements in the menu */
private void nodesChanged (final boolean check) {
java.util.List list = vis.getChildren ();
JPopupMenu popup = getPopupMenu();
boolean usedToBeContained = JPopupMenuUtils.isPopupContained (popup);
HashMap remove;
if (map == null) {
map = new HashMap (list.size ());
remove = new HashMap (0);
} else {
// objects to remove
remove = new HashMap (map);
}
Iterator it = list.iterator ();
while (it.hasNext ()) {
VisualizerNode v = (VisualizerNode)it.next ();
JMenuItem menu = (JMenuItem)map.get (v);
if (menu == null) {
menu = createMenuItem (Visualizer.findNode (v));
map.put (v, menu);
Menu.this.add (menu);
} else {
// do not remove the node
remove.remove (v);
}
}
it = remove.values ().iterator ();
while (it.hasNext ()) {
JMenuItem menu = (JMenuItem)it.next ();
// menu.setPopupMenuVisible (false);
Menu.this.remove (menu);
}
// work with empty element
JMenuItem mi = empty;
if (mi != null) {
if (getMenuComponentCount() > 1) {
Menu.this.remove (mi);
empty = null;
}
} else {
if (getMenuComponentCount () == 0) {
empty = new JMenuItem(
NbBundle.getBundle(MenuView.class).getString("EmptySubMenu")
);
empty.setEnabled(false);
Menu.this.add (empty);
}
}
popup.pack ();
popup.invalidate ();
Component c = popup.getParent ();
if (c != null) {
c.validate ();
}
JPopupMenuUtils.dynamicChangeToSubmenu(popup, usedToBeContained);
} // end nodesChanged
/** Method to check whether a component is part of selection.
*/
private static boolean isPart (MenuElement me) {
MenuSelectionManager msm = MenuSelectionManager.defaultManager ();
MenuElement[] path = msm.getSelectedPath ();
for (int i = 0; i < path.length; i++) {
if (me == path[i]) {
return true;
}
}
return false;
}
/** Listener to changes in nodes */
private final class Listener extends Object
implements PopupMenuListener, ChangeListener, NodeModel {
public void stateChanged(javax.swing.event.ChangeEvent p1) {
if (
Menu.this.isPopupMenuVisible () &&
!isPart (Menu.this)
) {
Menu.this.setPopupMenuVisible (false);
}
}
/** Notification of children addded event. Modifies the list of nodes
* and fires info to all listeners.
*/
public void added(VisualizerEvent.Added ev) {
getPopupMenu ().removePopupMenuListener (this);
nodesChanged (true);
getPopupMenu ().addPopupMenuListener (this);
}
/** Notification that children has been removed. Modifies the list of nodes
* and fires info to all listeners.
*/
public void removed(VisualizerEvent.Removed ev) {
getPopupMenu ().removePopupMenuListener (this);
nodesChanged (true);
getPopupMenu ().addPopupMenuListener (this);
}
/** Notification that children has been reordered. Modifies the list of nodes
* and fires info to all listeners.
*/
public void reordered(VisualizerEvent.Reordered ev) {
}
/** Update a visualizer (change of name, icon, description, etc.)
*/
public void update(VisualizerNode v) {
}
public void popupMenuWillBecomeInvisible(final javax.swing.event.PopupMenuEvent p1) {
}
public void popupMenuCanceled(final javax.swing.event.PopupMenuEvent p1) {
}
public void popupMenuWillBecomeVisible(final javax.swing.event.PopupMenuEvent p1) {
getPopupMenu ().removePopupMenuListener (this);
nodesChanged (false);
getPopupMenu ().addPopupMenuListener (this);
}
}
}
/** Acceptor that can be passed to constructor of {@link MenuView.Menu}.
* It permits determination of which nodes should be accepted upon a click.
*/
public static interface Acceptor {
/** Test whether to accept the node or not. Can also perform some actions (such as opening the node, etc.).
* @param n the node
* @return true if the <code>menu</code> should close
*/
public boolean accept (Node n);
}
// [PENDING] this should rather look for the node's default action! --jglick
/** default listener that opens explorer */
static final Acceptor DEFAULT_LISTENER = new Acceptor () {
public boolean accept (Node n) {
TopManager.NodeOperation op = TopManager.getDefault ().getNodeOperation ();
if (n.isLeaf ()) {
op.showProperties (n);
} else {
op.explore (n);
}
return true;
}
};
/** Menu item that can represent one node in the tree.
*/
public static class MenuItem extends JMenuItem {
/** generated Serialized Version UID */
static final long serialVersionUID = -918973978614344429L;
/** The node represented. */
protected Node node;
/** The action listener to attach to all menu items. */
protected Acceptor action;
/** Construct item for given node with the node's default action.
* @param node the node to represent
*/
public MenuItem (Node node) {
this (node, DEFAULT_LISTENER);
}
/** Construct item for given node, specifying an action.
* @param node the node to represent
* @param l the acceptor to decide whether to accept this node or not
*/
public MenuItem (Node node, Acceptor l) {
this (node, l, true);
}
/** Construct item for given node, specifying the action and whether to create the icon and name automatically.
* @param node the node to represent
* @param l the acceptor to decide whether to accept this node or not
* @param setName <code>false</code> if the name and icon should not be set
*/
public MenuItem (Node node, Acceptor l, boolean setName) {
super ();
this.node = node;
this.action = l;
if (setName) {
initialize (this, node);
}
HelpCtx help = node.getHelpCtx ();
if (help != null && ! help.equals (HelpCtx.DEFAULT_HELP) && help.getHelpID () != null)
HelpCtx.setHelpIDString (this, help.getHelpID ());
}
/** Inform the acceptor.
* @param time see superclass
*/
public void doClick (int time) {
action.accept (node);
}
/** Initialize an item for a node.
*/
static void initialize (JMenuItem item, Node node) {
item.setIcon (new ImageIcon(node.getIcon(java.beans.BeanInfo.ICON_COLOR_16x16)));
item.setText (node.getDisplayName ());
/*
item.setMargin(new java.awt.Insets(0, 0, 0, 0));
item.setHorizontalTextPosition(RIGHT);
item.setHorizontalAlignment(LEFT);
*/
}
}
}
/*
* Log
* 23 Gandalf 1.22 3/11/00 Martin Ryzl menufix [by E.Adams,
* I.Formanek]
* 22 Gandalf 1.21 1/11/00 Ian Formanek Removed 1.3 specific
* code, as the menu problem in JDK 1.3 is now working with the same code
* 21 Gandalf 1.20 12/2/99 Jaroslav Tulach On 1.3 does not use the
* setVisible false/true hack.
* 20 Gandalf 1.19 11/26/99 Patrik Knakal
* 19 Gandalf 1.18 11/5/99 Jaroslav Tulach WeakListener has now
* registration methods.
* 18 Gandalf 1.17 10/22/99 Ian Formanek NO SEMANTIC CHANGE - Sun
* Microsystems Copyright in File Comment
* 17 Gandalf 1.16 9/25/99 Jaroslav Tulach #3727
* 16 Gandalf 1.15 9/24/99 Jaroslav Tulach Does not blick so often
* when nodes are added/removed.
* 15 Gandalf 1.14 9/6/99 Jaroslav Tulach Closing submenu when
* menu is closed.
* 14 Gandalf 1.13 8/27/99 Jaroslav Tulach New threading model &
* Children.
* 13 Gandalf 1.12 7/27/99 Jaroslav Tulach Again and better
* 12 Gandalf 1.11 7/27/99 Jaroslav Tulach Should close all popup
* menus in the New from template
* 11 Gandalf 1.10 7/22/99 Jaroslav Tulach Handles
* lightweight/heavyweight state change.
* 10 Gandalf 1.9 7/16/99 Jesse Glick Context help.
* 9 Gandalf 1.8 6/28/99 Ian Formanek NbJMenu renamed to
* JMenuPlus
* 8 Gandalf 1.7 6/28/99 Ian Formanek Fixed bug 2043 - It is
* virtually impossible to choose lower items of New From Template from
* popup menu on 1024x768
* 7 Gandalf 1.6 6/10/99 Jaroslav Tulach
* 6 Gandalf 1.5 6/8/99 Ian Formanek ---- Package Change To
* org.openide ----
* 5 Gandalf 1.4 3/26/99 Ian Formanek Fixed use of obsoleted
* NbBundle.getBundle (this)
* 4 Gandalf 1.3 3/20/99 Jesse Glick [JavaDoc]
* 3 Gandalf 1.2 3/20/99 Jesse Glick [JavaDoc]
* 2 Gandalf 1.1 2/5/99 Jaroslav Tulach Changed new from
* template action
* 1 Gandalf 1.0 1/5/99 Ian Formanek
* $
* Beta Change History:
* 0 Tuborg 0.15 --/--/98 Jan Formanek added readObject, made serializable (hopefully)
* 0 Tuborg 0.16 --/--/98 Jan Formanek changed the predecessor from JPanel to Object, a new JPanel is
* 0 Tuborg 0.16 --/--/98 Jan Formanek returned from getComponent
* 0 Tuborg 0.20 --/--/98 Jan Formanek extends ExplorerViewSupport
* 0 Tuborg 0.30 --/--/98 Jan Formanek SWITCHED TO NODES
* 0 Tuborg 0.31 --/--/98 Jan Formanek got rid of Explorer.getExplorerBundle
* 0 Tuborg 0.40 --/--/98 Jan Formanek reflecting changes in explorer model
* 0 Tuborg 0.41 --/--/98 Jan Formanek reflecting changes in ExplorerView
*/